# coding=utf-8

import openpyxl
import yaml
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.worksheet import Worksheet
import csv
import pandas as pd
import chardet
from typing import Union, NoReturn


class Readfile(object):
    """
    说明：
        提供一些常用功能
        1. read_excel():
            读取Excel单元格数据
        2. write_excel
            写入Excel单元格数据
        3. read_csv():
            读取Csv单元格数据
        4. write_csv()
            写入Csv单元格数据
        5. get_file_coding()
            获取文件的编码格式
        6. read_yaml()
            读取yaml文件数据
    """

    @staticmethod
    def _return_cloumn(ws: Worksheet, p_column: Union[str, int]) -> str:
        """
        列数转换成字母形式
        :param ws: sheet页 type=openpyxl.worksheet.worksheet.Worksheet
        :param p_column: 列名或列数 type=int or str
        :return: 列数转换为字母形式
        """
        if isinstance(p_column, int):
            return get_column_letter(p_column)  # 数字转换为字母
        elif isinstance(p_column, str):
            row_list = list(map(lambda x: x.value, list(ws[1])))  # 解析第一行
            if row_list.count(p_column) == 1:  # 相同字段名存在的个数
                for index, cell in enumerate(row_list):
                    if cell == p_column:
                        return get_column_letter(index + 1)
            elif row_list.count(p_column) == 0:
                raise ValueError("当前sheet页“%s”不存在字段名为“%s”" % (ws.title, p_column))
            else:
                raise ValueError("当前sheet页“%s”存在多个字段名为“%s”" % (ws.title, p_column))
        else:
            raise TypeError(
                "column参数类型应为int或str类型而不是“%s”，int表示第几列，str表示第一行列字段名"
                % p_column.__class__
            )

    @classmethod
    def read_excel(cls, path: str, column: int or str = 1, row: int = 1, sheet: int or str = 1) -> str:
        """
        读取Excel某一单元格数据
        :param path: Excel路径 type=str
        :param sheet: 第几个sheet页或sheet页名称 type=int or str
        :param row: 行数 type=int
        :param column: 列数或列字段名 type=int or str
        :return: 单元格的值
        """
        if not isinstance(row, int):
            raise TypeError("row参数类型应为int类型而不是“%s”" % row.__class__)
        wb = openpyxl.load_workbook(path)
        if isinstance(sheet, str):
            ws = wb[sheet]
            column =cls._return_cloumn(ws=ws, p_column=column)
            return ws[column + str(row)].value
        elif isinstance(sheet, int):
            ws = wb[wb.sheetnames[sheet - 1]]
            column = cls._return_cloumn(ws=ws, p_column=column)
            return ws[column + str(row)].value
        else:
            raise TypeError(
                "sheet参数类型应为int或str类型而不是“%s”，数字表示为第几个sheet页，字符表示sheet页名称"
                % sheet.__class__
            )

    @classmethod
    def write_excel(cls, path: str, value: str = "", column: int = 1, row: int = 1, sheet: int = 1) -> NoReturn:
        """
        写入Excel指定单元格数据
        :param path: Excel路径 type=str
        :param sheet: 第几个sheet页或sheet页名称 type=int or str
        :param row: 行数 type=int
        :param column: 列数或列字段名 type=int or str
        :param value: 需要写入的数据 type=str
        """
        if not isinstance(row, int):
            raise TypeError("row参数类型应为int类型而不是“%s”" % row.__class__)
        if not isinstance(value, str):
            raise TypeError("value参数类型应为str类型而不是“%s”" % value.__class__)
        wb = openpyxl.load_workbook(path)
        if isinstance(sheet, str):
            ws = wb.get_sheet_by_name(sheet)
            column = cls._return_cloumn(ws=ws, p_column=column)
            ws[column + str(row)].value = value
            wb.save(path)
        elif isinstance(sheet, int):
            ws = wb.get_sheet_by_name(wb.sheetnames[sheet - 1])
            column = cls._return_cloumn(ws=ws, p_column=column)
            ws[column + str(row)].value = value
            wb.save(path)
        else:
            raise TypeError(
                "sheet参数类型应为int或str类型而不是“%s”，数字表示为第几个sheet页，字符表示sheet页名称"
                % sheet.__class__
            )

    @staticmethod
    def read_csv(path: str, column: int = 1, row: int = 1, fields: bool = True) -> str:
        """
        说明：读取csv某一单元格数据
        注意：文件编码建议使用utf-8
        :param path: csv文件路径 type=str
        :param row: 行数 type=int
        :param column: 字段名或列数 type=str or int
        :param fields: csv文件第一行是否是字段
        :return: 单元格值
        """

        if not isinstance(row, int):
            raise TypeError("row参数类型应为int类型而不是“%s”" % row.__class__)
        with open(path, "r", encoding="utf-8") as csv_file:  # 转为list
            csv_reader = csv.reader(csv_file)
            csv_list = list(csv_reader)
        with open(path, "r", encoding="utf-8") as csv_file:  # 转为dict
            csv_dict = csv.DictReader(csv_file)
            csv_dict_list = list(csv_dict)
        if fields:
            if isinstance(column, str):
                return csv_dict_list[row - 1][column]
            elif isinstance(column, int):
                return csv_list[row][column - 1]
            else:
                raise TypeError(
                    "csv中有字段时，可以使用字段名表示列名类型为str，当然也可以使用列数表示类型为int，但当前类型为“%s”"
                    % column.__class__
                )
        else:
            if not isinstance(column, int):
                raise TypeError("csv中没有字段，请使用int类型表示列数，当前类型为“%s”" % column.__class__)
            else:
                return csv_list[row - 1][column - 1]

    @staticmethod
    def write_csv(path: str, value: str = "", column: int = 1, row: int = 1, fields: bool = True) -> NoReturn:
        """
        说明：写入csv指定单元格数据
        注意：文件编码建议使用utf-8
        :param path: csv文件路径 type=str
        :param value: 需要写入的数据 type=str
        :param row: 行数 type=int
        :param column: 字段名或列数 type=str or int
        :param fields: csv文件第一行是否是字段
        :return: None
        """
        if not isinstance(row, int):
            raise TypeError("row参数类型应为int类型而不是“%s”" % row.__class__)
        if row == 1:
            raise ValueError("第一行为字段行，不允许修改...")
        df = pd.read_csv(path)
        if fields:
            if isinstance(column, str):
                df.at[row - 2, column] = value  # [行数，列名]
            elif isinstance(column, int):
                df.iat[row - 2, column - 1] = value  # [行数，列数]
            else:
                raise TypeError(
                    "csv中有字段时，可以使用字段名表示列名类型为str，当然也可以使用列数表示类型为int，但当前类型为“%s”"
                    % column.__class__
                )
        else:
            if not isinstance(column, int):
                raise TypeError("csv中没有字段，请使用int类型表示列数，当前类型为“%s”" % column.__class__)
            else:
                df.iat[row - 2, column - 1] = value
        df.to_csv(path_or_buf=path, index=False)

    @staticmethod
    def get_file_coding(filepath: str) -> dict:
        """
        说明：
            获取文件的编码格式
        :param filepath: 文件路径 type=str
        :return: {'confidence': 1.0, 'encoding': 'UTF-8'} type=dict
        """
        with open(file=filepath, mode="rb") as f:
            s = f.read()
        return chardet.detect(s)

    @staticmethod
    def read_yaml(path: str,):
        with open(path, "r", encoding="utf8") as f:
            data = yaml.load(f.read(), Loader=yaml.FullLoader)
        return data
